// TS教程： http://ts.xcatliu.com/basics/primitive-data-types.html
// https://www.jianshu.com/p/df14afcfdb03
/**
 * create boolean
 */
let isDone: boolean = false;
let createdByNewBoolean: Boolean = new Boolean(1);
let createdByBoolean: boolean = Boolean(1);

/**
 * =================
 *   缺布尔运算例子
 *   |、&、||、&&
 *   计算在实战中的应用
 * =================
 */
/**
 * create number
 */
let decLiteral: number = 6;
// 16进制
let hexLiteral: number = 0xf00d;
// 2进制
let binaryLiteral: number = 0b1010;
// 8进制
let octalLiteral: number = 0o744;
let notANumber: number = NaN;
let infinityNumber: number = Infinity;

/**
 * =================
 *   缺数值运算例子：
 *   1. 加减乘除
 *   2. 浮点运算及取整规则
 *   3. 位运算及应用
 *   4. 进制转换
 *   5. 常量及枚举类型表示
 * =================
 */


// create string
let myName: string = "Tom";
let myAge: number = 25;

// ES6模板字符串
let sentence: string = `Hello, my name is ${myName}, I will be ${myAge + 1} years old next month`;


// return void
function alertName(): void {
  alert('My Name is Tom')
}

// 声明一个 void 类型的变量没有什么用，因为你只能将它赋值为 undefined 和 null（只在 --strictNullChecks 未指定时）：
let unusable: void = undefined;

// Null & Undefined
let u: undefined = undefined;
let n: null = null;

// ** 与教程有出入
// 与 void 的区别是，undefined 和 null 是所有类型的子类型。也就是说 undefined 类型的变量，可以赋值给 number 类型的变量：
// * 不能将类型“undefined”分配给类型“number”。ts(2322)
// let num: number = undefined;

// * 无法重新声明块范围变量“u”。ts(2451)
// let u: undefined;

// * 不能将类型“undefined”分配给类型“number”。ts(2322)
// let numb: number = u;

/**
 * ========================================================
 *   字符串运算的例子
 *   字符串的range，搜索、(URL)Encode、(URL)Decode、Base64
 * ========================================================
 */


/**
 * 任意类型
 */
let myFavoriteNumber: string = 'seven';
// * 不能将类型“number”分配给类型“string”。ts(2322)
// myFavoriteNumber = 7;

let someNumber: any = 'seven';
someNumber = 7;
// 访问不存在的字段不受编译器约束
// 输出undefined
console.log(someNumber.myName)

// * 编译时出错：TypeError: Cannot read property 'firstName' of undefined
// console.log(someNumber.myName.firstName)

// * 编译时出错：TypeError: someNumber.getFirstName is not a function
// console.log(someNumber.getFirstName("Tom Jerry"))

let something: any;
// 输出undefined
console.log(typeof something);

let anything: any = false;
// 输出boolean
console.log(typeof anything);


/**
 * 联合类型
 */
let unionType: string | number;
unionType = 100;
unionType = '100percent';
console.log(unionType)


/**
 * 接口
 */
interface Person {
  name: string;
  age: number;
}

let tom: Person = {
  name: 'tom',
  age: 32
};

// * 类型 "{ name: string; }" 中缺少属性 "age"，但类型 "Person" 中需要该属性。ts(2741)
// let p: Person = {
//   name: 'Jerry'
// };

// * 不能将类型“{ name: string; age: number; address: string; }”分配给类型“Person”。
// * 对象文字可以只指定已知属性，并且“address”不在类型“Person”中。
// let p2: Person = {
//   name: "Charlie",
//   age: 32,
//   address: 'No.8 Avenu Beijing'
// };

/**
 * 可选属性
 */
interface People {
  name: string,
  age?: number
}

let judy: People = {
  name: "Judy",
  age: 33
};

let judy2: People = {
  name: "Judy2",
};

/**
 * 任意属性
 */
interface OnePerson {
  name: string;
  age?: number;
  [propName: string]: any;
}

// 一旦定义了任意属性，那么确定属性和可选属性的类型都必须是它的类型的子集：
let oneJudy: OnePerson = {
  name: "One Judy",
  age: 34,
  gendar: 'male'
};


/**
 * 只读类型
 */
interface Foreigner {
  readonly id: number;
  name: string;
  age?: number;
  [propName: string]: any;
}

// 必须要指定id
let caocao: Foreigner = {
  id: 123,
  name: "Caocao",
  gender: "male"
}
// 无法分配到 "id" ，因为它是只读属性。ts(2540)
// caocao.id = 234;

/**
 * ================================
 *   缺接口在API强类型定义上的例子
 * ================================
 */


/**
 * 数组类型
 */
let fibonacci: number[] = [1, 2, 3, 5, 8];

// 不能将类型“string”分配给类型“number”。ts(2322)
// let fibonacci2: number[] = [1, 2, 3, '5', 8];

// 类型“string”的参数不能赋给类型“number”的参数。ts(2345)
// fibonacci.push('11');

let fibo3: Array<number> = [1, 2, 3, 5, 8];

/**
 * 接口表示数组
 */
interface NumberArray {
  [index: number]: number;
}

let fibo4: NumberArray = [1, 1, 2, 3, 5];


// 类数组
// 类数组（Array-like Object）不是数组类型，比如 arguments：
function sum() {
  // 类型“IArguments”缺少类型“number[]”的以下属性: pop, push, concat, join 及其他 27 项。ts(2740)
  // let args: number[] = arguments;

  let args: {
    [index: number]: number;
    length: number;
    callee: Function;
  } = arguments;

}

/**
 * 
 * interface IArguments {
 *  [index: number]: any;
 *  length: number;
 *  callee: Function;
 * }
 * --------------------------
 * interface IArguments {
 *   // Iterator
 *   [Symbol.iterator](): IterableIterator<any>;
 * }
 */
//  ECMAScript 的内置对象§
//  ECMAScript 标准提供的内置对象有：
//  Boolean、Error、Date、RegExp 等。
function sum2() {
  let args: IArguments = arguments;
}

// any 在数组中的应用
// 一个比较常见的做法是，用 any 表示数组中允许出现任意类型：
let list: any[] = ['xcatliu', 25, { website: 'http://xcatliu.com' }];


/**
 * 函数生命
 */
// 参数“x”隐式具有“any”类型。ts(7006)
// function sum3(x, y) {
//   return x + y;
// }

// 函数表达式（Function Expression）
// 参数“x”隐式具有“any”类型。ts(7006)
// let mySum = function (x, y) {
//   return x + y;
// };

function sum3(x: number, y: number): number {
  return x + y;
}
// 多参数报错
// sum3(1, 2, 3);

let mySum3 = function (x: number, y: number): number {
  return x + y;
};

// 应有 2 个参数，但获得 3 个。ts(2554)
// mySum3(1,2,3);


/**
 * 注意不要混淆了 TypeScript 中的 => 和 ES6 中的 =>。
 * 在 TypeScript 的类型定义中，=> 用来表示函数的定义，左边是输入类型，需要用括号括起来，右边是输出类型。
 * 在 ES6 中，=> 叫做箭头函数，应用十分广泛，可以参考 ES6 中的箭头函数。
 */
let mySum: (x: number, y: number) => number = function (x: number, y: number): number {
  return x + y;
};

// 用接口定义函数的形状
interface SearchFunc {
  (source: string, subString: string): boolean;
}

let mySearch: SearchFunc;
mySearch = function (source: string, subString: string) {
  return source.search(subString) !== -1;
}


// 可选参数 & 参数默认值
function buildName(firstName: string = 'Tom', lastName?: string) {
  if (lastName) {
    return firstName + ' ' + lastName;
  } else {
    return firstName;
  }
}
let tomcat = buildName('Tom', 'Cat');
let tomCat = buildName('Tom');

// 必选参数不能位于可选参数后。ts(1016)
// function buildName2(firstName?: string, lastName: string) {
//   if (firstName) {
//       return firstName + ' ' + lastName;
//   } else {
//       return lastName;
//   }
// }
// 类型“undefined”的参数不能赋给类型“string”的参数。ts(2345)
// let tomThecat = buildName(undefined, 'Tom');


/**
 * 剩余参数
 */
// ES6 中，可以使用 ...rest 的方式获取函数中的剩余参数（rest 参数）：
function push(array: any[], ...items: any[]) {
  items.forEach(function (item) {
    array.push(item);
  });
}

let a: any[] = [];
push(a, 1, 2, 3);

/**
 * 函数重载
 */
function reverse(x: number | string): number | string | void {
  if (typeof x === 'number') {
    return Number(x.toString().split('').reverse().join(''));
  } else if (typeof x === 'string') {
    return x.split('').reverse().join('');
  }
}


/**
 * 然而这样有一个缺点，就是不能够精确的表达，输入为数字的时候，输出也应该为数字，输入为字符串的时候，输出也应该为字符串。
 * 这时，我们可以使用重载定义多个 reverse 的函数类型：
 */
// function reverse(x: number): number;
// function reverse(x: string): string;
// function reverse(x: number | string): number | string | void {
//     if (typeof x === 'number') {
//         return Number(x.toString().split('').reverse().join(''));
//     } else if (typeof x === 'string') {
//         return x.split('').reverse().join('');
//     }
// }


/**
 * 类型断言
 */
interface Cat {
  name: string;
  run(): void;
}

interface Fish {
  name: string;
  swim(): void;
}

function getAnimalName(animal: Cat | Fish) {
  return animal.name;
}

// 类型“Cat | Fish”上不存在属性“swim”。
// 类型“Cat”上不存在属性“swim”。ts(2339)
// function isFish(animal: Cat | Fish) {
//   if (typeof animal.swim === 'function') {
//     return true;
//   }
//   return false;
// }

function isFish(animal: Cat | Fish) {
  if (typeof (animal as Fish).swim === 'function') {
    return true;
  }
  return false;
}

let catOne: Cat = {
  name: "Kitty",
  run() {
    console.log("I am running...");
  }
}
console.log(isFish(catOne))
let fishOne: Fish = {
  name: "Kitty",
  swim() {
    console.log("I am swimming...");
  }
}
console.log(isFish(fishOne))


/**
 * 将父类断言为更为具体的子类
 */
class ApiError extends Error {
  code: number = 0;
}

class HttpError extends Error {
  statusCode: number = 200;
}

function isApiError(error: Error) {
  if (typeof (error as ApiError).code === 'number') {
    return true;
  }
  return false;
}


/**
 * 内置对象
 */
let bb: Boolean = new Boolean(1);
let ee: Error = new Error("Error Occurred");
let dd: Date = new Date();
let rr: RegExp = /[a-z]*/;

// node 没有 document.
// let body: HTMLElement = document.body;
// let allDiv: NodeList = document.querySelectorAll('div');
// document.addEventListener('click', function (e: MouseEvent) {
//   // Do something.
// });



Math.pow(10, 2);

// Node.js 不是内置对象的一部分，如果想用 TypeScript 写 Node.js，则需要引入第三方声明文件：
// npm install @types/node --save-dev


/**
 * 类型别名
 */
type Name = string;
type NameResolver = () => string;
type NameOrResolver = Name | NameResolver;

function getName(n: NameOrResolver): Name {
  if (typeof n === 'string') {
    return n;
  } else {
    return n();
  }
}


/**
 * 字面量类型
 */
type EventNames = 'click' | 'scroll' | 'mousemove';
function handleEvent(ele: Element, event: EventNames) {
  // do something.
}


/**
 * 元组 Tuple
 */
let tom9: [string, number] = ['Tom', 25];
console.log(tom9[0] + ' is ' + tom9[1] + ' years old.');

tom9[0] = 'Old Tom';
tom9[1] = 35;
console.log(tom9[0] + ' is ' + tom9[1] + ', ready to retire.');

tom9 = ['Great Tom', 40];
console.log(tom9[0] + ' is ' + tom9[1] + ', ready to die.');

// 不能将类型“[string]”分配给类型“[string, number]”。源具有 1 个元素，但目标需要 2 个。ts(2322)
// tom9 = ['Tom'];

tom9.push('male');
// 长度为 "2" 的元组类型 "[string, number]" 在索引 "2" 处没有元素。ts(2493)
console.log(tom9[0] + ' is ' + tom9[1] + ', ready to die.' + tom9[2]);

// 类型“boolean”的参数不能赋给类型“string | number”的参数。ts(2345)
// tom9.push(true);


/**
 * 枚举
 */
enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

console.log(Days["Sun"] === 0); // true
console.log(Days["Mon"] === 1); // true
console.log(Days["Tue"] === 2); // true
console.log(Days["Sat"] === 6); // true

console.log(Days[0] === "Sun"); // true
console.log(Days[1] === "Mon"); // true
console.log(Days[2] === "Tue"); // true
console.log(Days[6] === "Sat"); // true



enum Days2 {Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat};

console.log(Days2["Sun"] === 7); // true
console.log(Days2["Mon"] === 1); // true
console.log(Days2["Tue"] === 2); // true
console.log(Days2["Sat"] === 6); // true

enum Days3 {Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat};

console.log(Days3["Sun"] === 3); // true
console.log(Days3["Wed"] === 3); // true
console.log(Days3[3] === "Sun"); // false
console.log(Days3[3] === "Wed"); // true



enum Color {Red, Green, Blue = "blue".length};
// { '0': 'Red', '1': 'Green', '4': 'Blue', Red: 0, Green: 1, Blue: 4 }
console.log(Color)


const enum Directions {
  Up,
  Down,
  Left,
  Right
}

// 常数枚举与普通枚举的区别是，它会在编译阶段被删除，并且不能包含计算成员。
// [js]: let directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];
let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

// 外部枚举与声明语句一样，常出现在声明文件中。
// 同时使用 declare 和 const 也是可以的：
declare const enum Directions2 {
  Up,
  Down,
  Left,
  Right
}
// 外部枚举与声明语句一样，常出现在声明文件中。
// ReferenceError: Directions2 is not defined
// let directions2 = [Directions2.Up, Directions2.Down, Directions2.Left, Directions2.Right];


class Animal {
  // error TS2300: Duplicate identifier 'name'.
  public name;

  static isAnimal(a) {
    return a instanceof Animal;
  }

  // 参数“name”隐式具有“any”类型。ts(7006)
  constructor(name) {
    this.name = name;
  }

  // 使用 getter 和 setter 可以改变属性的赋值和读取行为：
  get name() {
    return 'set Jack';
  }

  set name(value) {
    console.log('setter: ' + value);
  }

  sayHi() {
    return `My name is ${this.name}`;
  }
}

let aa = new Animal('Doge');
console.log(aa.sayHi());


class Cattie extends Animal {
  constructor(name) {
    super(name);
    console.log(this.name);
  }
  sayHi() {
    return 'Meow, ' + super.sayHi();
  }
}

let cc = new Cattie('Tom');
cc.name = 'Cattie Tom'
console.log(cc.sayHi());
console.log(Animal.isAnimal(cc));

